package proj2501.net_logo;

import java.awt.Color;
import java.text.DecimalFormat;
import java.util.Hashtable;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;

public class NLColor {
	private static int NUM_HUES = 14;
	private static int MAX_COLOR = 10 * NUM_HUES;
	private static String[] COLOR_NAMES = {
		"gray", "red", "orange", "brown", "yellow",
		"green", "lime", "turquoise", "cyan", "sky",
		"blue", "violet", "magenta", "pink",
		"black", "white",
	};
	private static double BLACK_NUM = 0;
	private static double WHITE_NUM = 9.9;
	private static Double BOXED_BLACK = BLACK_NUM;
	private static Double BOXED_WHITE = WHITE_NUM;
	private static double[] COLOR_NUMBERS = {
		5, 15, 25, 35, 45,
		55, 65, 75, 85, 95,
		105, 115, 125, 135,
		BLACK_NUM, WHITE_NUM,
	};
	private static int[] COLORS_RGB = {
		140, 140, 140, // 5: gray
		215, 48, 39, // 15: red
		241, 105, 19, // 25: orange
		156, 109, 70, // 35: brown
		237, 237, 47, // 45: yellow
		87, 176, 58, // 55: green
		42, 209, 57, // 65: lime
		27, 158, 119, // 75: turquoise
		82, 196, 196, // 85: cyan
		43, 140, 190, // 95: sky
		50, 92, 168, // 105: blue
		123, 78, 163, // 115: violet
		166, 25, 105, // 125: magenta
		224, 126, 149, // 135: pink
		0, 0, 0, // black
		255, 255, 255, // white
	};
	private static int[] ARGB_CACHE;
	{
		int maxColorTimesTen = MAX_COLOR * 10;
		int[] ints = new int[maxColorTimesTen];
		for (int i = 0; i < maxColorTimesTen; i++) {
			ints[i] = computeARGBforCache(i);
		}
		ints[0] = 0xff << 24;
		ints[99] = 0xffffffff;
		ARGB_CACHE = ints;
	}
	private static Color[] AWT_CACHE;
	{
		int maxColorTimesTen = MAX_COLOR * 10;
		Color[] colors = new Color[maxColorTimesTen];
		for (int i = 0; i < maxColorTimesTen; i++) {
			int argb = getARGBbyPremodulatedColorNumber(i / 10.0);
			colors[i] = new Color(argb);
		}
		AWT_CACHE = colors;
	}
	private static Color getColor(double color) {
		return AWT_CACHE[(int)(color * 10)];
	}
	private static Hashtable<Double, Double> RGB_MAP;
	{
		int maxColorTimesTen = MAX_COLOR * 10;
		Hashtable<Double, Double> map = new Hashtable<Double, Double>();
		for (int i = 0; i < maxColorTimesTen; i++) {
			double color = i / 10.0;
			map.put((double) getARGBbyPremodulatedColorNumber(color), color);
		}
		RGB_MAP = map;
	}
	private static double getColorNumberByIndex(int index) {
		return COLOR_NUMBERS[index];
	}
	private static String[] getColorNamesArray() { return COLOR_NAMES; }
	private static String getColorNameByIndex(int index) { return COLOR_NAMES[index]; }
	private static int modulateInteger(int color) {
		if (color < 0 || color >= MAX_COLOR) {
			color %= MAX_COLOR;
			if (color < 0) {
				color += MAX_COLOR;
			}
		}
		return color;
	}
	private static double modulateDouble(double color) {
		if (color < 0 || color >= MAX_COLOR) {
			color %= MAX_COLOR;
			if (color < 0) {
				color += MAX_COLOR;
			}
			if (color >= MAX_COLOR) {
				color = 139.9999999999999;
			}
		}
		return color;
	}
	private static double findCentralColorNumber(double color) {
		double c;
		if (color < 0 || color >= MAX_COLOR) {
			c = modulateDouble(color);
		} else {
			c = color;
		}
		return ((int) (c / 10) + 0.5) * 10;
	}
	private static double getClosestColorNumberByARGB(int argb) {
		double d = estimateClosestColorNumberByRGB(argb);
		return RGB_MAP.get(d);
	}
	private static String getClosestColorNameByARGB(int argb) {
		DecimalFormat formatter = new DecimalFormat("###.####");
		double closest = getClosestColorNumberByARGB(argb);
		if (closest == BLACK_NUM) {
			return getColorNameByIndex(14);
		} else if (closest == WHITE_NUM) {
			return getColorNameByIndex(15);
		} else {
			int baseColorNumber = (int) findCentralColorNumber(closest);
			double difference = closest - baseColorNumber;
			String baseColorName = getColorNameByIndex((baseColorNumber - 5) / 10);
			if (difference == 0) {
				return baseColorName;
			} else if (difference > 0) {
				return baseColorName + " + " + formatter.format(StrictMath.abs(difference));
			} else {
				return baseColorName + " - " + formatter.format(StrictMath.abs(difference));
			}
		}
	}
	private static double getClosestColorNumberByHSB(float h, float s, float b) {
		if (h < 0) {
			h = 0;
		} else if (255 < h) {
			h = 255;
		}
		if (s < 0) {
			s = 0;
		} else if (255 < s) {
			s = 255;
		}
		if (b < 0) {
			b = 0;
		} else if (255 < b) {
			b = 255;
		}
		int argb = Color.HSBtoRGB(h / 255, s / 255, b / 255);
		double d = estimateClosestColorNumberByRGB(argb);
		return RGB_MAP.get(d);
	}
	private static double estimateClosestColorNumberByRGB(int argb) {
		long smallestDistance = 100000000L;
		double closest = 0;
		Set<Entry<Double, Double>> entries = RGB_MAP.entrySet();
		for (Entry<Double, Double> entry: entries) {
			double key = entry.getKey();
			double value = entry.getValue();
			int candidate = (int) key;
			long dist = colorDistance(argb, candidate);
			if (dist < smallestDistance) {
				smallestDistance = dist;
				closest = value;
			}
		}
		return closest;
	}
	private static long colorDistance(int argb1, int argb2) {
		int r1 = argb1 >> 16 & 0xff;
		int g1 = argb1 >> 8 & 0xff;
		int b1 = argb1 & 0xff;
		int r2 = argb2 >> 16 & 0xff;
		int g2 = argb2 >> 8 & 0xff;
		int b2 = argb2 & 0xff;
		int rmean = (r1 + r2) / 2;
		int rd = r1 - r2;
		int gd = g1 - g2;
		int bd = b1 - b2;
		return (((512 + rmean) * rd * rd) >> 8) + 4 * gd * gd + (((767 - rmean) * bd * bd) >> 8);
	}
	private static int getARGBbyPremodulatedColorNumber(double modulatedColor) {
		return ARGB_CACHE[(int)(modulatedColor * 10)];
	}
	private static int computeARGBforCache(int colorTimesTen) {
		int baseIndex = colorTimesTen / 100;
		int r = COLORS_RGB[baseIndex * 3];
		int g = COLORS_RGB[baseIndex * 3 + 1];
		int b = COLORS_RGB[baseIndex * 3 + 2];
		double step = (colorTimesTen % 100 - 50) / 50.48 + 0.012;
		if (step < 0) {
			r += r * step;
			g += g * step;
			b += b * step;
		} else if (step > 0) {
			r += (0xff - r) * step;
			g += (0xff - g) * step;
			b += (0xff - b) * step;
		}
		return (0xff << 24) + (r << 16) + (g << 8) + b;
	}
	private static int getRGBByName(String name) {
//		boolean found = false;
//		int index = 0;
//		for (int i = 0; i < COLOR_NAMES.length; i++) {
//			if (COLOR_NAMES[i].equals(name)) {
//				found = true;
//				index = i;
//			}
//		}
//		if (! found) {
//			throw new RuntimeException();
//		}
//		return getARGBByIndex(index);
		return getARGBByIndex(COLOR_NAMES_INDEX.get(name));
	}
	private static int getARGBByIndex(int index) {
		if (index == 14) {
			return 0xff000000;
		} else if (index == 15) {
			return 0xffffffff;
		} else {
			return ARGB_CACHE[index * 100 + 50];
		}
	}
	
	////////////////////////////////////////////////
	private static Hashtable<String, Integer> COLOR_NAMES_INDEX;
	{
		COLOR_NAMES_INDEX = new Hashtable<String, Integer>();
		for (int i = 0; i < COLOR_NAMES.length; i++) {
			COLOR_NAMES_INDEX.put(COLOR_NAMES[i], i);
		}
	}
	private static Hashtable<String, Double> COLOR_NAMES_TO_NUMBER;
	{
		COLOR_NAMES_TO_NUMBER = new Hashtable<String, Double>();
		for (int i = 0; i < COLOR_NAMES.length; i++) {
			COLOR_NAMES_TO_NUMBER.put(COLOR_NAMES[i], COLOR_NUMBERS[i]);
		}
	}
	public static final NLColor GRAY = new NLColor("gray");
	public static final NLColor RED = new NLColor("red");
	public static final NLColor ORANGE = new NLColor("orange");
	public static final NLColor BROWN = new NLColor("brown");
	public static final NLColor YELLOW = new NLColor("yellow");
	public static final NLColor GREEN = new NLColor("green");
	public static final NLColor LIME = new NLColor("lime");
	public static final NLColor TURQUOISE = new NLColor("turquoise");
	public static final NLColor CYAN = new NLColor("cyan");
	public static final NLColor SKY = new NLColor("sky");
	public static final NLColor BLUE = new NLColor("blue");
	public static final NLColor VIOLET = new NLColor("violet");
	public static final NLColor MAGENTA = new NLColor("magenta");
	public static final NLColor PINK = new NLColor("pink");
	public static final NLColor BLACK = new NLColor("black");
	public static final NLColor WHITE = new NLColor("white");
	private static final NLColor[] PRIMARY_COLORS = {
		GRAY, RED, ORANGE, BROWN, YELLOW,
		GREEN, LIME, TURQUOISE, CYAN, SKY,
		BLUE, VIOLET, MAGENTA, PINK,
	};
	public static NLColor getFrom14Primary() {
		int r = random.nextInt(PRIMARY_COLORS.length);
		return PRIMARY_COLORS[r];
	}
	
	private double colorNumber;
	private int argb;
	private Color swingColor;
	public Color getSwingColor() { return swingColor; }

	public NLColor(double colorNumber) {
		construct(colorNumber);
	}
	
	public NLColor(String colorName) {
		double colorNumber = COLOR_NAMES_TO_NUMBER.get(colorName);
		construct(colorNumber);
	}
	
	private void construct(double colorNumber) {
		double modulated = modulateDouble(colorNumber);
		argb = getARGBbyPremodulatedColorNumber(modulated);
		swingColor = getColor(colorNumber);
	}
	
	private static Random random = new Random();
}
